package models

import (
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/gitee-go/core"
	"gitee.com/gitee-go/core/model/pipeline"
	"gitee.com/gitee-go/core/runtime"
	"gitee.com/gitee-go/utils/ioex"
	"regexp"
	"strings"
	"time"
)

type YML struct {
	Pie         *Pipeline `json:"pie"`
	YmlContent  string    `yaml:"-" json:"-"`
	JsonContent string    `yaml:"-" json:"-"`
}

type Pipeline struct {
	Id          string                        `json:"Id"  yaml:"-"`
	BuildId     string                        `json:"buildId"  yaml:"-" `
	Name        string                        `yaml:"name,omitempty" json:"name"`
	Version     string                        `yaml:"version,omitempty" json:"version"`
	DisplayName string                        `yaml:"displayName,omitempty" json:"displayName"`
	Triggers    map[string]*Trigger           `yaml:"triggers,omitempty" json:"triggers"`
	Variables   map[string]string             `yaml:"variables,omitempty" json:"variables"`
	Vars        map[string]*runtime.Variables `yaml:"-" json:"-"`
	Stages      []*Stage                      `yaml:"stages,omitempty" json:"stages"`
}

type Trigger struct {
	AutoCancel     bool       `yaml:"autoCancel,omitempty" json:"autoCancel,omitempty"`
	Timeout        string     `yaml:"timeout,omitempty" json:"timeout,omitempty"`
	Branches       *Condition `yaml:"branches,omitempty" json:"branches,omitempty"`
	Tags           *Condition `yaml:"tags,omitempty" json:"tags,omitempty"`
	Paths          *Condition `yaml:"paths,omitempty" json:"paths,omitempty"`
	Notes          *Condition `yaml:"notes,omitempty" json:"notes,omitempty"`
	CommitMessages *Condition `yaml:"commitMessages,omitempty" json:"commitMessages,omitempty"`
}

type Condition struct {
	Include []string `yaml:"include,omitempty" json:"include,omitempty"`
	Exclude []string `yaml:"exclude,omitempty" json:"exclude,omitempty"`
}

type Stage struct {
	Stage             string            `yaml:"stage" json:"stage"`
	Id                string            `json:"Id"   yaml:"-"`
	PipelineVersionId string            `json:"pipelineVersionId"   yaml:"-"`
	BuildId           string            `json:"buildId"   yaml:"-"`
	Name              string            `yaml:"name,omitempty" json:"name"`
	DisplayName       string            `yaml:"displayName,omitempty" json:"displayName"`
	Status            string            `json:"status"   yaml:"-"`
	Error             string            `json:"error"   yaml:"-"`
	ExitCode          int               `json:"exit_code"   yaml:"-"`
	Started           time.Time         `json:"started"   yaml:"-"`
	Stopped           time.Time         `json:"stopped"   yaml:"-"`
	Finished          time.Time         `json:"finished"   yaml:"-"`
	Created           time.Time         `json:"created"   yaml:"-"`
	Updated           time.Time         `json:"updated"   yaml:"-"`
	Version           string            `json:"version"   yaml:"-"`
	OnSuccess         bool              `json:"on_success"   yaml:"-"`
	OnFailure         bool              `json:"on_failure"   yaml:"-"`
	Labels            map[string]string `json:"labels,omitempty" yaml:"-"`
	Jobs              []*Job            `yaml:"steps,omitempty" json:"steps"`
}

type Job struct {
	Job               string            `yaml:"step" json:"step"`
	Id                string            `json:"id"  yaml:"-"`
	BuildId           string            `json:"buildId"   yaml:"-"`
	StageId           string            `json:"stageId"  yaml:"-"`
	PipelineVersionId string            `json:"pipelineVersionId"  yaml:"-"`
	DisplayName       string            `yaml:"displayName,omitempty" json:"displayName"`
	Name              string            `yaml:"name,omitempty" json:"name"`
	Environments      map[string]string `yaml:"environments,omitempty" json:"environments"`
	PlugVars          map[string]string `yaml:"plugVars,omitempty" json:"plugVars"`
	Commands          []interface{}     `yaml:"commands,omitempty" json:"commands"`
	Command           string            `yaml:"-" json:"-"`
	Number            int               `json:"number"  yaml:"-"`
	Status            string            `json:"status"  yaml:"-"`
	Created           time.Time         `json:"created"   yaml:"-"`
	Error             string            `json:"error,omitempty"  yaml:"-"`
	ErrIgnore         bool              `json:"errignore,omitempty"  yaml:"-"`
	ExitCode          int               `json:"exitCode"  yaml:"-"`
	Started           time.Time         `json:"started,omitempty"  yaml:"-"`
	Stopped           time.Time         `json:"stopped,omitempty"  yaml:"-"`
	Finished          time.Time         `json:"finished"  yaml:"-"`
	Version           string            `json:"version"  yaml:"-"`
	DependsOn         []string          `yaml:"dependsOn,omitempty" json:"dependsOn"`
	Image             string            `yaml:"image,omitempty" json:"image"`
	Artifacts         []*Artifact       `yaml:"artifacts,omitempty" json:"artifacts"`
	DependArtifacts   []*DependArtifact `yaml:"dependArtifacts,omitempty" json:"dependArtifacts"`
	Plugined          bool              `yaml:"-" json:"plugined"`
}

type Artifact struct {
	Id        string `json:"id"  yaml:"-"`
	BuildId   string `json:"buildId"  yaml:"-"`
	StageId   string `json:"stageId"  yaml:"-"`
	JobId     string `json:"stepId"  yaml:"-"`
	BuildName string `json:"buildName"  yaml:"-"`
	StageName string `json:"stageName"  yaml:"-"`
	JobName   string `json:"stepName"  yaml:"-"`

	Name  string `yaml:"name,omitempty" json:"name"`
	Scope string `yaml:"scope,omitempty" json:"scope"`
	Path  string `yaml:"path,omitempty" json:"path"`

	Repository string `yaml:"repository,omitempty" json:"repository"`

	Value string `yaml:"value,omitempty" json:"value"`
}

type DependArtifact struct {
	BuildId   string `yaml:"buildId,omitempty" json:"buildId"`
	StageId   string `yaml:"stageId,omitempty" json:"stageId"`
	JobId     string `yaml:"stepId,omitempty" json:"stepId"`
	BuildName string `yaml:"buildName,omitempty" json:"buildName"`
	StageName string `yaml:"stageName,omitempty" json:"stageName"`
	JobName   string `yaml:"jobName,omitempty" json:"stepName"`

	Type       string `yaml:"type,omitempty" json:"type"`
	Repository string `yaml:"repository,omitempty" json:"repository"`
	Name       string `yaml:"name,omitempty" json:"name"`
	Target     string `yaml:"target,omitempty" json:"target"`
	IsForce    bool   `yaml:"isForce,omitempty" json:"isForce"`

	SourceStage string `yaml:"sourceStage,omitempty" json:"sourceStage"`
	SourceJob   string `yaml:"sourceStep,omitempty" json:"sourceStep"`

	Value string `json:"value"  yaml:"-"`
}

type PluginPipeline struct {
	Stages []*PluginStage `yaml:"stages,omitempty" json:"stages"`
}
type PluginStage struct {
	Stage string                   `yaml:"stage" json:"stage"`
	Jobs  []map[string]interface{} `yaml:"steps,omitempty" json:"steps"`
}

func (c *YML) ParseYML() (*YML, error) {
	return c, nil
}
func (c *YML) BuildToRuntime(t *pipeline.TBuild) (*runtime.Build, error) {
	b := &runtime.Build{}
	err := ioex.StructToStructByMap(t, b)
	if err != nil {
		core.Log.Error(err)
		return nil, err
	}

	rPie := new(runtime.Pipeline)
	err = ioex.StructToStructByMap(c.Pie, rPie)
	if err != nil {
		core.Log.Error(err)
		return nil, err
	}
	ma := new(runtime.Manifest)
	ma.Pipe = rPie
	b.Cfg = ma
	return b, nil
}

func (c *YML) Check() error {
	if c.Pie == nil {
		return errors.New("流水线为空")
	}
	stages := make(map[string]map[string]*Job)
	if c.Pie.Stages == nil || len(c.Pie.Stages) <= 0 {
		return errors.New("stages 为空")
	}
	for _, v := range c.Pie.Stages {
		if v.Name == "" {
			return errors.New("stages name 为空")
		}
		if v.Jobs == nil || len(v.Jobs) <= 0 {
			return errors.New("step 为空")
		}
		if _, ok := stages[v.Name]; ok {
			return errors.New(fmt.Sprintf("build stages.%s 重复", v.Name))
		}
		m := map[string]*Job{}
		stages[v.Name] = m
		for _, e := range v.Jobs {
			if strings.TrimSpace(e.Job) == "" {
				return errors.New("step 插件为空")
			}
			if e.Name == "" {
				return errors.New("step name 为空")
			}
			if _, ok := m[e.Name]; ok {
				return errors.New(fmt.Sprintf("steps.%s 重复", e.Name))
			}
			m[e.Name] = e
		}
	}
	return nil
}

func (c *Condition) Match(v string) bool {
	if c == nil {
		return false
	}
	if c.Include != nil && c.Exclude != nil {
		return c.Includes(v) && !c.Excludes(v)
	}

	if c.Include != nil && c.Includes(v) {
		return true
	}

	if c.Exclude != nil && !c.Excludes(v) {
		return true
	}

	return false
}

func (c *Condition) Excludes(v string) bool {
	for _, in := range c.Exclude {
		if in == "" {
			continue
		}
		if in == v {
			return true
		}
		if isMatch(v, in) {
			return true
		}
		reg, err := regexp.Compile(in)
		if err != nil {
			return false
		}
		match := reg.Match([]byte(strings.Replace(v, "\n", "", -1)))
		if match {
			return true
		}
	}
	return false
}

func (c *Condition) Includes(v string) bool {
	for _, in := range c.Include {
		if in == "" {
			continue
		}
		if in == v {
			return true
		}
		if isMatch(v, in) {
			return true
		}
		reg, err := regexp.Compile(in)
		if err != nil {
			return false
		}
		match := reg.Match([]byte(strings.Replace(v, "\n", "", -1)))
		if match {
			return true
		}
	}
	return false
}
func isMatch(s string, p string) bool {
	m, n := len(s), len(p)
	dp := make([][]bool, m+1)
	for i := 0; i <= m; i++ {
		dp[i] = make([]bool, n+1)
	}
	dp[0][0] = true
	for i := 1; i <= n; i++ {
		if p[i-1] == '*' {
			dp[0][i] = true
		} else {
			break
		}
	}
	for i := 1; i <= m; i++ {
		for j := 1; j <= n; j++ {
			if p[j-1] == '*' {
				dp[i][j] = dp[i][j-1] || dp[i-1][j]
			} else if s[i-1] == p[j-1] {
				dp[i][j] = dp[i-1][j-1]
			}
		}
	}
	return dp[m][n]
}

func (c *YML) conversionCommands() {
	for _, stage := range c.Pie.Stages {
		for _, job := range stage.Jobs {
			if job.Commands == nil {
				continue
			}
			job.conversionCommands()
		}
	}
}
func (c *YML) Marshal() ([]byte, error) {
	c.conversionCommands()
	return json.Marshal(c.Pie)
}
func (c *Job) conversionCommands() {
	var ls []interface{}
	for _, command := range c.Commands {
		switch command.(type) {
		case string:
			ls = append(ls, command.(string))
		case map[interface{}]interface{}:
			cm := command.(map[interface{}]interface{})
			sm := make(map[string][]string)
			for k, v := range cm {
				switch v.(type) {
				case []interface{}:
					var strList []string
					for _, vv := range v.([]interface{}) {
						switch vv.(type) {
						case string:
							strList = append(strList, vv.(string))
						default:
							core.Log.Warnf("the commands err: %v", k)
						}
					}
					sm[fmt.Sprintf("%v", k)] = strList
					break
				default:
					core.Log.Warnf("the commands err : %v", k)
				}
			}
			ls = append(ls, sm)
		case []interface{}:
			ls = append(ls, command.([]interface{}))
		case interface{}:
			cm := command.(map[string]interface{})
			sm := make(map[string][]string)
			for k, v := range cm {
				switch v.(type) {
				case []interface{}:
					var strList []string
					for _, vv := range v.([]interface{}) {
						switch vv.(type) {
						case string:
							strList = append(strList, vv.(string))
						default:
							core.Log.Warnf("the commands err: %v", k)
						}
					}
					sm[k] = strList
					break
				default:
					core.Log.Warnf("the commands err : %v", k)
				}
			}
			ls = append(ls, sm)
		default:
			core.Log.Warn("the commands err ")
		}
	}
	c.Commands = ls
}
